﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic.FileIO;
using Microsoft.VisualBasic.MyServices;
using Microsoft.VisualBasic.Devices;
using System.Threading;

namespace CopyIt
{
    class Syncronizer
    {
        private OverrideMode Override;
        private FileAttributes excludeTypes;
        private Wildcard[] includeWC;
        private Wildcard[] excludeWC;
        private string[] IncludeFilter = null;
        private string[] ExcludeFilter = null;
        private bool EnableInclude = false;
        private bool EnableExclude = false;

        private string[] IncludeFolder = null;
        private string[] ExcludeFolder = null;
        private Wildcard[] includeFLD;
        private Wildcard[] excludeFLD;
        private bool EnableInclFLD = false;
        private bool EnableExclFLD = false;

        private WriteMode Writemode;
        private BackgroundWorker bWork = null;
        private int FileTrovati = 0;
        private int FileAnalizzati = 0;

        public event EventHandler BeginSyncronize;
        public event EventHandler EndSyncronize; 
        public delegate void SyncronizeHandler(Object sender, SyncronizerEventArgs e);
        public event SyncronizeHandler FileSyncronize;
        public event SyncronizeHandler AddNode;

        public Syncronizer()
        {
            bWork = new BackgroundWorker();
            bWork.WorkerSupportsCancellation = true;
            bWork.DoWork += new DoWorkEventHandler(bWork_DoWork);
            bWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bWork_RunWorkerCompleted);

            //CopyIt to trash
            //Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(path, Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
        }

        public void FindFiles(List<PairsParameters> Params)
        {
            if (BeginSyncronize != null)
                BeginSyncronize(this, new EventArgs());

            FileTrovati = 0;
            FileAnalizzati = 0;
            bWork.RunWorkerAsync(Params);
        }

        public void FindFiles(PairsParameters Params)
        {
            if (BeginSyncronize != null)
                BeginSyncronize(this, new EventArgs());

            FileTrovati = 0;
            FileAnalizzati = 0;
            bWork.RunWorkerAsync(Params);
        }

        public void StopSyncronize()
        {
            if (MessageBox.Show("Sei sicuro di voler fermare l'analisi ?", "Stop dell'utente", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                bWork.CancelAsync();
        }

        void bWork_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (EndSyncronize != null)
                EndSyncronize(this, new EventArgs());
        }

        void bWork_DoWork(object sender, DoWorkEventArgs e)
        {
            if (e.Argument.GetType() == typeof(PairsParameters))
            {
                PairsParameters Params = (PairsParameters)e.Argument;
                DoFindFiles(Params);
            }
            else
            {
                List<PairsParameters> ParamList = (List<PairsParameters>)e.Argument;
                foreach (PairsParameters pairs in ParamList)
                    if (pairs.Checked)
                        DoFindFiles(pairs);
            }
        }

        private void SetParams(PairsParameters Params)
        {
            Override = Params.Override;
            Writemode = Params.Writemode;

            EnableInclude = Params.EnableIncludeFilter;
            IncludeFilter = Params.CurrentIncludeFilter.Split(new char[] { ';' });
            includeWC = new Wildcard[IncludeFilter.Length];
            for (int t = 0; t < IncludeFilter.Length; t++)
                includeWC[t] = new Wildcard(IncludeFilter[t], RegexOptions.IgnoreCase);

            EnableExclude = Params.EnableExcludeFilter;
            ExcludeFilter = Params.CurrentExcludeFilter.Split(new char[] { ';' });
            excludeWC = new Wildcard[ExcludeFilter.Length];
            for (int t = 0; t < excludeWC.Length; t++)
                excludeWC[t] = new Wildcard(ExcludeFilter[t], RegexOptions.IgnoreCase);

            EnableInclFLD = Params.EnableInclFolder;
            IncludeFolder = Params.CurrentInclFolder.Split(new char[] { ';' });
            includeFLD = new Wildcard[IncludeFolder.Length];
            for (int t = 0; t < IncludeFolder.Length; t++)
                includeFLD[t] = new Wildcard(IncludeFolder[t], RegexOptions.IgnoreCase);

            EnableExclFLD = Params.EnableExclFolder;
            ExcludeFolder = Params.CurrentExclFolder.Split(new char[] { ';' });
            excludeFLD = new Wildcard[ExcludeFolder.Length];
            for (int t = 0; t < ExcludeFolder.Length; t++)
                excludeFLD[t] = new Wildcard(ExcludeFolder[t], RegexOptions.IgnoreCase);

            excludeTypes = Params.ExcludeTypes;
        }

        private void DoFindFiles(PairsParameters Params)
        {
            if (!System.IO.Directory.Exists(Params.SourceFolder) || !System.IO.Directory.Exists(Params.DestFolder))
                return;

            SetParams(Params);
            ExTreeNode firstNode = DoFindFiles(Params.SourceFolder, Params.SourceFolder, Params.DestFolder, Params.Writemode);

            if (AddNode != null && firstNode != null)
            {
                firstNode.Guid = Params.Guid;
                AddNode(this, new SyncronizerEventArgs(firstNode));
            }
        }

        private ExTreeNode DoFindFiles(string root, string currDir, string dest, WriteMode write)
        {
            ExTreeNode treeNode = null;

            // Controllo che esista la cartella
            string destDir = CreateDestDirPath(root, currDir, dest);

            treeNode = new ExTreeNode();
            treeNode.Text = CreateDestDirTree(currDir);
            treeNode.SourcePath = destDir;
            treeNode.Override = Directory.Exists(destDir) ? OverrideFile.None : OverrideFile.Create;
            treeNode.ImageIndex = treeNode.Override == OverrideFile.None ? 1 : 0;
            treeNode.SelectedImageIndex = treeNode.ImageIndex;
            treeNode.Checked = true;

            if (FileSyncronize != null)
                FileSyncronize(this, new SyncronizerEventArgs(currDir, FileTrovati, FileAnalizzati));

            if (bWork.CancellationPending)
                return null;
            
            // Guardo tutti i files dentro ad essa
            try
            {
                foreach (string file in Directory.GetFiles(currDir))
                {
                    string destFile = CreateDestFilePath(root, file, dest);
                    FileAnalizzati++;
                    if (CheckExtensionFile(destFile) && CheckPropertyFile(file))
                    {
                        bool fExist = File.Exists(destFile);
                        if (!fExist || CanOverride(file, destFile))
                        {
                            ExTreeNode node = new ExTreeNode();
                            node.Text = Path.GetFileName(destFile);
                            node.Override = fExist ? OverrideFile.Replace : OverrideFile.Add;
                            node.Write = node.Override == OverrideFile.Replace ? write : WriteMode.Override;
                            node.ImageIndex = node.Override == OverrideFile.Replace ? 3 : 2;
                            node.SelectedImageIndex = node.ImageIndex;
                            node.SourcePath = file;
                            node.DestPath = destFile;
                            FileInfo fFile = new FileInfo(file);
                            node.Size = (ulong)fFile.Length;
                            //fFile.Attributes == FileAttributes.System;
                            node.Checked = true;
                            treeNode.Nodes.Add(node);

                            FileTrovati++;
                        }
                    }

                    if (FileSyncronize != null)
                        FileSyncronize(this, new SyncronizerEventArgs(currDir, FileTrovati, FileAnalizzati));
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Attenzione", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
                

            //Cerco dentro a tutte le cartelle
            foreach (string folder in Directory.GetDirectories(currDir))
            {
                if (!CheckFolder(folder))
                    continue;
                ExTreeNode nodeInside = DoFindFiles(root, folder, dest, write);
                if (nodeInside != null)
                    treeNode.Nodes.Add(nodeInside);
            }
            if (treeNode.Nodes.Count != 0)
                return treeNode;
            else
                return null;
        }

        private string CreateDestFilePath(string root, string file, string dest)
        {
            if (!file.Contains(root))
                Debug.Assert(false);

            string extPath = file.Substring(root.Length);
            if (extPath.StartsWith("\\"))
                extPath = extPath.Substring(1);

            string destPath = Path.Combine(dest, extPath);

            return destPath;
        }

        private string CreateDestDirPath(string root, string curr, string dest)
        {
            if (!curr.Contains(root))
                Debug.Assert(false);

            string extPath = curr.Substring(root.Length);
            if (extPath.StartsWith("\\"))
                extPath = extPath.Substring(1);
            string destPath = Path.Combine(dest, extPath);

            return destPath;
        }

        private string CreateDestDirTree(string curr)
        {
            string parent = Path.GetDirectoryName(curr);
            string extPath = parent != null ? curr.Substring(parent.Length) : "";
            if (extPath.StartsWith("\\"))
                extPath = extPath.Substring(1);

            return extPath;
        }

        private bool CheckPropertyFile(string file)
        {
            if (excludeTypes != 0)
            {
                FileInfo fFile = new FileInfo(file);
                FileAttributes fA = fFile.Attributes & excludeTypes;
                return ((fFile.Attributes & excludeTypes) == 0);
            }

            return true;
        }

        private bool CheckFolder(string file)
        {
            bool IsOk = false;
            if (!EnableInclFLD && !EnableExclFLD)
                return true;

            string curFile = Path.GetFileName(file);

            if (EnableInclFLD)
                for (int t = 0; t < includeFLD.Length; t++)
                    IsOk |= includeFLD[t].IsMatch(curFile);
            else IsOk = true;

            if (IsOk && EnableExclFLD)
                for (int t = 0; t < excludeFLD.Length; t++)
                    IsOk &= !excludeFLD[t].IsMatch(curFile);

            return IsOk;
        }

        private bool CheckExtensionFile(string file)
        {
            bool IsOk = false;
            if (!EnableInclude && !EnableExclude)
                return true;

            string curFile = Path.GetFileName(file);

            if (EnableInclude)
                for (int t = 0; t < includeWC.Length; t++)
                    IsOk |= includeWC[t].IsMatch(curFile);
            else IsOk = true;

            if (IsOk && EnableExclude)
                for (int t = 0; t < excludeWC.Length; t++)
                    IsOk &= !excludeWC[t].IsMatch(curFile);

            return IsOk;
        }

        private bool CanOverride(string file, string dest)
        {
            switch (Override)
            {
                case OverrideMode.OverrideData:
                    if (File.GetLastWriteTime(file) > File.GetLastWriteTime(dest))
                        return true;
                    break;
                case OverrideMode.OverrideDiff:
                    FileInfo fFile = new FileInfo(file);
                    FileInfo fDest = new FileInfo(dest);
                    if (fFile.Length != fDest.Length)
                        return true;
                    break;
                case OverrideMode.None:
                    return false;
                case OverrideMode.Override:
                    return true;
            }

            return false;
        }
    }

    public class SyncronizerEventArgs : EventArgs
    {
        public string Directory { get; private set; }
        public ExTreeNode Node { get; private set; }
        public int FileFound { get; private set; }
        public int FileAnalized { get; private set; } 

        public SyncronizerEventArgs(string dir, int fileTrovati, int fileAnalizzati)
        {
            Directory = dir;
            FileFound = fileTrovati;
            FileAnalized = fileAnalizzati;
        }

        public SyncronizerEventArgs(ExTreeNode node)
        {
            Node = node;
        }
    }
}
